package com.ttpai.boot.lbs.service;

import com.ttpai.boot.lbs.dao.MetroPointDAO;
import com.ttpai.boot.lbs.dao.MetroPointTimeDAO;
import com.ttpai.boot.lbs.model.MetroPointRawVO;
import com.ttpai.boot.lbs.model.MetroPointTimeVO;
import com.ttpai.boot.lbs.model.MetroPointVO;
import com.ttpai.boot.lbs.model.RoutePlanReportVO;
import com.ttpai.boot.lbs.util.CalendarUtil;
import com.ttpai.boot.lbs.util.LLUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * Created by Kail on 2017/3/2.
 * Copyright(c) ttpai All Rights Reserved.
 */
@Service
public class MetroPointPlanRouteRealService {

    private static final Logger logger = LoggerFactory.getLogger(MetroPointPlanRouteRealService.class);

    private static final int CHECK_USE_MIN = 29; // 检测一辆车需要多长时间

    @Autowired
    private RoutePlanReportService reportService;
    @Autowired
    private MetroPointDAO metroPointDAO;

    @Autowired
    private MetroPointTimeDAO metroPointTimeDAO;
    @Autowired
    private MetroPointRawService metroPointRawService;

    /**
     * 查询一个城市的所有地铁站
     */
    public List<MetroPointVO> selectMetroPointByCityName(String cityName) {
        MetroPointVO select = new MetroPointVO();
        select.setCityName(cityName + "%");
        return metroPointDAO.selectAllMetroPoint(select);
    }

    public Map<String, List<RoutePlanReportVO>> buildMetroDynamicPlain(Date date, String city) {
        List<List<RoutePlanReportVO>> doPlanNotFormat = this.doPlanNotFormat(date, city);

        Map<String, List<RoutePlanReportVO>> map = CommonService.buildDynamicPlain(doPlanNotFormat);

        buildOnePointToOtherPoint(map);

        return map;
    }

    private List<List<RoutePlanReportVO>> doPlanNotFormat(Date date, String city) {

        List<RoutePlanReportVO> reportVOS = this.selectNearMetro(date, city); // 当前一个城市所有的数据，每个点包含离其最近的地铁站信息


        List<List<RoutePlanReportVO>> planRoutes = new ArrayList<>();

        for (RoutePlanReportVO startPoint : reportVOS) {
            if (null == startPoint.getLng() || startPoint.getLng().equals(0D)) {
                continue;
            }
            // 从第一个开始，如果没有被分配，则该点为起点
            if (!startPoint.getZhanyong()) {
                ArrayList<RoutePlanReportVO> planLine = new ArrayList<>();
                planLine.add(startPoint);
                planRoutes.add(planLine);

                // 从起点开始查找下一条路线
                startPoint.setTempText("起点");
                findNextPointFromStart(planLine, startPoint, reportVOS);

            }
        }
        return planRoutes;
    }

    /**
     * @param line       当前路线
     * @param startPoint 开始路线
     * @param plainDatas 原始数据
     */
    void findNextPointFromStart(List<RoutePlanReportVO> line, RoutePlanReportVO startPoint, List<RoutePlanReportVO> plainDatas) {
        startPoint.setZhanyong(true); // 标记起始点为占用状态

        // 遍历所有原始数据，判断下一条路线
        for (int i = 0; i < plainDatas.size(); i++) {
            RoutePlanReportVO curPoint = plainDatas.get(i);
            if (null == curPoint.getLng() || curPoint.getLng().equals(0D)) {
                continue;
            }
            // 起始点的检测结束时间
            Date checkEndTime = CalendarUtil.add(startPoint.getCheckTime(), CHECK_USE_MIN, Calendar.MINUTE);
            // 开始点的检测结束时间为启程时间
            long starTime = checkEndTime.getTime();

            // 没有被占用，并且 检测时间是在第一辆车检测完成之后
            if (!curPoint.getZhanyong() && !curPoint.getCheckTime().before(checkEndTime)) {
                Long minCost = Long.MAX_VALUE; // 找到下一点的做小成本 = 消除距离后的时间差(时间最短) + 路程耗时(距离最短)
                RoutePlanReportVO nextPoint = null; // 找出下一个点
                Boolean inMetro = true;

                for (int j = i; j < plainDatas.size(); j++) {
                    RoutePlanReportVO cur = plainDatas.get(j);
                    if (!cur.getZhanyong()) {
                        // 两个距离直接需要的秒数
                        double secNotMetro = LLUtil.rightAngleDistance2Sec(startPoint.getLng(), startPoint.getLat(), cur.getLng(), cur.getLat());
                        double metroSec = startPoint.getMetroOffLineTime()
                                + cur.getMetroOffLineTime()
                                + getMetroPointTime(startPoint.getBranchCityName(), startPoint.getMetroLineName(), cur.getMetroLineName());

                        double sec = secNotMetro < metroSec ? secNotMetro : metroSec;

                        // 结束时间 - 路程所需时间，消除路程所需时间
                        long endTime = CalendarUtil.add(cur.getCheckTime(), -(int) sec, Calendar.SECOND).getTime();

                        if (starTime < endTime) {
                            // 消除距离后的时间差(时间最短) + 路程耗时(距离最短，距离权重大于时间全总)
                            long curCost = (endTime - starTime) / 1000 + (long) sec * 5; // 1043735
                            if (curCost < minCost) {
                                minCost = curCost; //
                                nextPoint = cur;
                                inMetro = secNotMetro > metroSec;
                                cur.setMetroOnLineTime((long) metroSec);
                            }
                        }
                    }
                }

                // 遍历所有点，如果找的话就继续，找不到的话不找了
                if (null != nextPoint) {
                    line.add(nextPoint); // 放入路线中
                    if (!inMetro) {
                        nextPoint.setTempText("不走地铁");
                    }
                    findNextPointFromStart(line, nextPoint, plainDatas); // 找的的点作为起始点，开始找下一个
                    break; // 没找到下一个，就不找了，开始计算下一条路线
                }
            }
        }
    }

    /**
     * 把单点归都其他点上
     */
    static void buildOnePointToOtherPoint(Map<String, List<RoutePlanReportVO>> plan) {
        List<String> onePointLine = new ArrayList<>();
        for (Map.Entry<String, List<RoutePlanReportVO>> entry : plan.entrySet()) {
            if (entry.getValue().size() <= 2) {
                onePointLine.add(entry.getKey());
            }
        }

        for (String lineName : onePointLine) {
            ss(onePointLine, lineName, plan);
        }
    }

    private static void ss(List<String> onePointLine, String lineName, Map<String, List<RoutePlanReportVO>> allLine) {
        List<RoutePlanReportVO> singleLine = allLine.get(lineName);
        int findCount = 0;
        for (RoutePlanReportVO singlePoint : singleLine) {

            double minDis = Long.MAX_VALUE;
            String minLine = null;
            int minLineIndex = 0;

            for (Map.Entry<String, List<RoutePlanReportVO>> entry : allLine.entrySet()) {
                // 单线不跟单线合并
                if (!onePointLine.contains(entry.getKey())) {
                    List<RoutePlanReportVO> value = entry.getValue();
                    for (int i = 0; i < value.size(); i++) {
                        RoutePlanReportVO v = value.get(i);
                        if (v.getCheckTime().before(singlePoint.getCheckTime())) {
                            double linearDistance = LLUtil.getLinearDistance(singlePoint.getLng(), singlePoint.getLat(), v.getLng(), v.getLat()) * 5000 + Math.abs(v.getCheckTime().getTime() - singlePoint.getCheckTime().getTime());
                            if (linearDistance < minDis) {
                                minLine = entry.getKey();
                                minDis = linearDistance;
                                minLineIndex = i;
                            }
                        }
                    }

                }
            }
            // 找到了最小的Line
            if (StringUtils.isNotBlank(minLine)) {
                singlePoint.setAddress(singlePoint.getAddress() + "【【单点(一条线路不到三个任务)合并】】");
                singlePoint.setCheckTime(CalendarUtil.add(singlePoint.getCheckTime(), 7, Calendar.MINUTE));
                allLine.get(minLine).add(minLineIndex + 1, singlePoint);
                findCount++;
            }
        }
        allLine.remove(lineName);
    }

    /**
     * 查询地铁站间耗时（秒钟）
     *
     * @param cityName       城市
     * @param pointNameStart 起始点
     * @param pointNameEnd   结束点
     */
    Map<MetroPointTimeVO, MetroPointTimeVO> cache = null;

    public Integer getMetroPointTime(String cityName, String pointNameStart, String pointNameEnd) {

        if (null == cache || cache.isEmpty()) {
            List<MetroPointTimeVO> pointTimeVOS = metroPointTimeDAO.selectAllMetroPoint(new MetroPointTimeVO(cityName, "", ""));
            cache = new HashMap<>();
            for (MetroPointTimeVO pointTimeVO : pointTimeVOS) {
                cache.put(pointTimeVO, pointTimeVO);
            }
        }

        MetroPointTimeVO pointTimeVO = cache.get(new MetroPointTimeVO(cityName, pointNameStart, pointNameEnd));
        if (null != pointTimeVO) {
            return Math.abs(pointTimeVO.getUseTime());
        } else {
            logger.warn("未找到：" + cityName + "--" + pointNameStart + "--" + pointNameEnd);
        }

        return 20 * 60; // 十分钟
    }


    /* ========================================================================================================================================================= */
    /* ========================================================================================================================================================= */
    /* ========================================================================================================================================================= */

    /**
     * 查找近地铁
     */
    public List<RoutePlanReportVO> selectNearMetro(Date date, String cityName) {
        List<RoutePlanReportVO> routePlanReports = reportService.selectRoutePlanReportByDate(date, cityName.replace("市", ""));
        List<MetroPointVO> metroPoints = this.selectMetroPointByCityName(cityName);

        for (RoutePlanReportVO routePlanReport : routePlanReports) {
            long minTime = Long.MAX_VALUE;
            for (MetroPointVO metroPoint : metroPoints) {
                double linearDistance2Sec = LLUtil.linearDistance2Sec(metroPoint.getLng(), metroPoint.getLat(), routePlanReport.getLng(), routePlanReport.getLat());
                if (linearDistance2Sec < minTime) {
                    minTime = (long) linearDistance2Sec;
                    routePlanReport.setMetroLineName(metroPoint.getName());
                    routePlanReport.setMetroOffLineTime(minTime);
                    routePlanReport.setMetroLng(metroPoint.getLng());
                    routePlanReport.setMetroLat(metroPoint.getLat());
                    routePlanReport.setTempText("'" + metroPoint.getName() + "'" + (linearDistance2Sec / 60) + "分," + LLUtil.getLinearDistance(metroPoint.getLng(), metroPoint.getLat(), routePlanReport.getLng(), routePlanReport.getLat()) + "公里");
                }
            }
        }
        return routePlanReports;
    }

    // 抓取 地铁站之间的耗时(通过测试用例抓取)
    public void fetchMetroUseTime(Integer code, String cityName) {
        List<MetroPointVO> metroPoints = this.selectMetroPointByCityName(cityName);

        List<MetroPointTimeVO> pointTimesInDb = metroPointTimeDAO.selectAllMetroPoint(new MetroPointTimeVO(cityName, "", ""));
        Map<MetroPointTimeVO, MetroPointTimeVO> cache = new HashMap<>();
        for (MetroPointTimeVO pointTimeVO : pointTimesInDb) {
            cache.put(pointTimeVO, pointTimeVO);
        }

        List<MetroPointRawVO> metroPointRawVOS = metroPointRawService.fetchBaiduMetorCity(code, cityName);
        Map<String, MetroPointRawVO> map = new HashMap<>();
        for (MetroPointRawVO metroPointRawVO : metroPointRawVOS) {
            map.put(metroPointRawVO.getName(), metroPointRawVO);
        }


        for (MetroPointVO m1 : metroPoints) {
            for (MetroPointVO m2 : metroPoints) {
                if (m1.getName().equals(m2.getName())) {
                    MetroPointTimeVO metroPointTimeVO = new MetroPointTimeVO(cityName, m1.getName(), m2.getName(), 0);
                    if (!cache.containsKey(metroPointTimeVO) || !cache.get(metroPointTimeVO).getUseTime().equals(0)) {
                        metroPointTimeDAO.insertMetroPoint(metroPointTimeVO);
                        cache.put(metroPointTimeVO, metroPointTimeVO);
                    }
                } else {

                    MetroPointTimeVO pointTime1 = new MetroPointTimeVO(cityName, m1.getName(), m2.getName());
                    MetroPointTimeVO pointTime2 = new MetroPointTimeVO(cityName, m2.getName(), m1.getName());

                    if (!cache.containsKey(pointTime1) || !cache.containsKey(pointTime2) || cache.get(pointTime1).getUseTime().equals(0) || cache.get(pointTime2).getUseTime().equals(0)) {
                        MetroPointRawVO start = map.get(m1.getName());
                        MetroPointRawVO end = map.get(m2.getName());

                        Integer useTime = -20;

                        try {
                            useTime = metroPointRawService.fetchMetroPointTime(
                                    start.getUid(), start.getX(), start.getY(), start.getName(),
                                    end.getUid(), end.getX(), end.getY(), end.getName(),
                                    code
                            );
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }

                        pointTime1.setUseTime(useTime);
                        pointTime2.setUseTime(useTime);

                        metroPointTimeDAO.insertMetroPoint(pointTime1);
                        cache.put(pointTime1, pointTime1);
                        metroPointTimeDAO.insertMetroPoint(pointTime2);
                        cache.put(pointTime2, pointTime2);
                    }

                }
            }
        }
    }

}
